Sintassi per reti combinatorie
Una rete combinatoria si esprime come un module
composto solo da wire
, espressioni combinatorie e componenti che sono a loro volta reti combinatorie.
module
Il blocco module ... endmodule
definisce un tipo di componente, che può poi essere instanziato in altri componenti.
La dichiarazione di un module
include il suo nome e la lista delle sue porte.
module nome_rete ( porta1, porta2, ... );
...
endmodule
input
e output
Per ciascuna porta di un module
, dichiariamo se è di input
o output
, e di quanti bit è composta.
Se non specificata, la dimensione default è 1.
La dichiarazione di porte con le stesse caratteristiche si può fare nella stessa riga.
Le porte input
sono dei wire
il cui valore va assegnato al di fuori di questa rete.
Le porte output
sono dei wire
il cui valore va assegnato all'interno di questa rete.
module nome_rete ( porta1, porta2, porta3, porta4 );
input [3:0] porta1, porta2;
output [3:0] porta3;
output porta4;
...
endmodule
inout
Non usiamo porte inout
nelle reti combinatorie.
wire
Un wire
è un filo che trasporta un valore logico.
Se non specificata, la dimensione default è 1.
La dichiarazione di wire
con le stesse caratteristiche si può fare nella stessa riga.
wire [3:0] w1, w2;
wire w3, w4, w5;
Con uno statement assign
possiamo associare al wire
una espressione combinatoria: il wire
assumerà continuamente il valore dell'espressione, rispondendo ai cambiamenti dei suoi operandi.
Lo statement assign
può includere un fattore di ritardo, #T
, per indicare che il valore del filo segue il valore dell'espressione con ritardo di T
unità.
assign #1 w5 = w3 & w4;
Un wire
può essere associato a una porta di un module
, come mostrato nella sezione successiva.
Usare un module
in un altro module
Una volta definito un module
, possiamo instanziare componenti di questo tipo in un altro module.
nome_module nome_istanza (
.porta1(...), .porta2(...), ...
);
All'interno degli statement .porta(...)
specichiamo quale porta, espressione o wire
del module
corrente va collegato alla porta del module
instanziato.
Insieme agli statement assign
e l'uso di wire
, questo ci permette di comporre reti combinatorie su diversi livelli di complessità e con poca duplicazione del codice.
Come esempio, costruiamo un and
a 1 ingresso e lo usiamo per comporre un and
a 3 ingressi.
module and(a, b, z);
input a, b;
output z;
assign #1 z = a & b;
endmodule
module and2(a, b, c, z);
input a, b, c;
output z;
wire z1;
and a1(
.a(a), .b(b),
.z(z1)
);
and a2(
.a(c), .b(z1),
.z(z)
);
endmodule
Tabelle di verità
Talvolta il modo più immediato per esprimere una rete combinatoria è tramite la sua tabella di verità. È anche noto che data una tabella di verità possiamo ottenere una sintesi della rete combinatoria, utilizzando metodi come le mappe di Karnaugh.
In Verilog, il modo più immediato di esprimere una tabella di verità è utilizzando una catena di operatori ternari.
module and (x, y, z);
input x, y;
output z;
assign #1 z =
({x,y} == 2'b00) ? 1'b0 :
({x,y} == 2'b00) ? 1'b0 :
({x,y} == 2'b00) ? 1'b0 :
/*{x,y} == 2'b11*/ 1'b1;
Un'alternativa è l'uso di function
e casex
.
module and (x, y, z);
input x, y;
output z;
assign #1 z = tabella_verita({a, b});
function tabella_verita;
input [1:0] ab;
casex(ab)
2'b00: tabella_verita = 1'b0;
2'b01: tabella_verita = 1'b0;
2'b10: tabella_verita = 1'b0;
2'b11: tabella_verita = 1'b1;
endcase
endfunction
endmodule
Per indicare tabelle di verità con più di un bit in uscita si scrive, per esempio, function [1:0] tabella_verita;
.
Nel casex
si può utilizzare anche un caso default, scrivendo come ultimo caso default: tabella_verita = ...;
.
function
Le function
sono blocchi di codice da eseguire, parti del behavioral modelling di Verilog.
Il simulatore ne svolge i passaggi come un programma, senza consumare tempo e senza alcun corrispettivo hardware previsto.
È per questo, per esempio, che dobbiamo specificare noi il tempo consumato nello statement assign
.
L'uso mostrato qui delle function
è l'unico ammesso per una sintesi di reti combinatorie.
In presenza di ogni altra elaborazione algoritmica, di cui non sia evidente il corrispettivo hardware, sarà invece considerata una descrizione di rete combinatoria.
Multiplexer
I multiplexer sono da considerarsi noti e sintetizzabili, e si possono esprimere con uno o più operatori ternari ?
.
La sintassi è della forma cond ? v_t : v_f
, dove cond
è un predicato (espressione true
o false
) mentre v_t
e v_f
sono espressioni dello stesso tipo.
L'espressione ha valore v_t
se il predicato cond
è true
, v_f
altrimenti.
Per un multiplexer con selettore a 1 bit, basterà un solo ?
.
input sel;
assign #1 multiplexer = sel ? x0 : x1;
Per un selettore a più bit si dovranno usare in serie per gestire più casi
input [1:0] sel;
assign #1 multiplexer =
(sel == 2'b00) ? x0 :
(sel == 2'b01) ? x1 :
(sel == 2'b10) ? x2 :
/*sel == 2'b11*/ x3 :
Reti parametrizzate
In un module
si possono definire parametri per generalizzare la rete.
In particolare, questo è frequentemente utilizzato in reti_standard.v
per fornire reti il cui dimensionamento è da specificare.
Per esempio, vediamo come è definita una rete di somma a N bit.
module add(
x, y, c_in,
s, c_out, ow
);
parameter N = 2;
input [N-1:0] x, y;
input c_in;
output [N-1:0] s;
output c_out, ow;
assign #1 {c_out, s} = x + y + c_in;
assign #1 ow = (x[N-1] == y[N-1]) && (x[N-1] != s[N-1]);
endmodule
Con N = 2
viene impostato il valore di default del parametro.
Quando instanziamo la rete altrove, possiamo modificare questo parametro, per esempio per ottenere un sommatore a 8 bit.
add #( .N(8) ) a (
...
);
Un module
può avere più di un parametro, che possono essere impostati indipendentemente.
nome_modulo #( .nome_parametro1(v1), .nome_parametro2(v2)... ) nome_istanza (
...
);
I parametri determinano la quantità di hardware, che non è mutabile! I valori associati devono essere costanti.
La parametrizzazione è facilmente applicabile a descrizioni di reti combinatorie dove si usano espressioni combinatorie che il simulatore è facilmente in grado di adattare a diverse quantità di bit.
È molto più complicato applicarla a sintesi di reti combinatorie, dato che non si possono instanziare componenti in modo parametrico, per esempio N
full adder da 1 bit per sintetizzare un full adder a N
bit.